package scatter.common.rest.config;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor;
import com.baomidou.mybatisplus.core.toolkit.AES;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;

import java.util.Arrays;
import java.util.HashMap;

/**
 * 自定义mybatis plus配置加密处理
 * 因为在新版本的 idea版本中（2020.3.2）junit测试并有program arguments选项
 * 兼容vm options
 * 该类启动配置在spring.factories
 * 注意：该类目前发现在打成jar包时，直接运行jar会有以下问题：
 * 1. 如果jar包外有配置文件且有加密配置，这将导致外部配置文件失效，也就是说启动jar加了-Dmpw:key=xxxx,将导致外部配置失效,这时可以使用参数spring.config.location指定配置文件解决
 * 2. 一般在idea中开发没有额外的配置文件不影响
 * 3. 会发现该类中的日志会不生效，也就是说不会打在日志文件里，因为日志相关配置还没有准备好，
 * Created by yangwei
 * Created at 2021/3/31 13:05
 */
@Order(Ordered.LOWEST_PRECEDENCE)
public class CustomSafetyEncryptProcessor extends SafetyEncryptProcessor {


    /**
     * log 自动注入，不需要关心，如果使用@Slfj注解，日志打不出来
     */
    private final Log logger;

    private static String key = "mpw.key";
    private static String prefix = "mpw:";
    private static int prefixLength = prefix.length();


    public CustomSafetyEncryptProcessor(Log logger) {
        this.logger = logger;
        logger.info("CustomSafetyEncryptProcessor enabled");
    }

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        /**
         * 命令行中获取密钥
         */
        String mpwKey = null;
        for (PropertySource<?> ps : environment.getPropertySources()) {
            if (ps instanceof SimpleCommandLinePropertySource) {
                SimpleCommandLinePropertySource source = (SimpleCommandLinePropertySource) ps;
                mpwKey = source.getProperty(key);
                break;
            }
        }
        if (StringUtils.isBlank(mpwKey)) {
            mpwKey = System.getProperty(key);
        }
        /**
         * 处理加密内容
         */
        if (StringUtils.isNotBlank(mpwKey)) {
            logger.info(StrUtil.format("存在参数 {} 开始处理,将配置文件参数解密处理",key));
            HashMap<String, Object> map = new HashMap<>();
            for (PropertySource<?> ps : environment.getPropertySources()) {
                if (ps instanceof OriginTrackedMapPropertySource) {
                    OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource) ps;
                    boolean hasMpw = hasMpw(source);
                    if (hasMpw) {
                        map.clear();
                        for (String name : source.getPropertyNames()) {
                            Object value = source.getProperty(name);
                            if (value instanceof String) {
                                String str = (String) value;
                                if (str.startsWith(prefix)) {
                                    map.put(name, AES.decrypt(str.substring(prefixLength), mpwKey));
                                }else {
                                    map.put(name,value);
                                }
                            }else {
                                map.put(name,value);
                            }
                        }
                        // 将解密的数据放入环境变量，并处于第一优先级上
                        if (CollectionUtils.isNotEmpty(map)) {
                            environment.getPropertySources().addBefore(source.getName(),new MapPropertySource("custom_" + source.getName(), map));
                        }
                    }

                }
            }

        }else {
            logger.info(StrUtil.format("不存在参数 {} 已忽略,不进行配置文件解密处理",key));
        }
    }

    /**
     * 判断source 中是否存在 mpw 需要解密的值
     * @param source
     * @return
     */
    private boolean hasMpw(OriginTrackedMapPropertySource source){

        long count = Arrays.stream(source.getPropertyNames())
                .map(name -> source.getProperty(name))
                .filter(value -> value instanceof String)
                .filter(value -> ((String) value).startsWith(prefix))
                .map(Object::toString)
                .count();
        return count > 0;

    }
}
